package com.ruoyi.business.service.impl;

import com.ruoyi.business.config.WebSocketServer;
import com.ruoyi.business.domain.RescueTask;
import com.ruoyi.business.domain.Shop;
import com.ruoyi.business.mapper.RescueTaskMapper;
import com.ruoyi.business.service.IRescueTaskService;
import com.ruoyi.business.service.IShopService;
import com.ruoyi.business.uitls.AddressResolution;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.domain.geo.Metrics;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 应急救援管理Service业务层处理
 *
 * @author bcl
 * @date 2024-09-12
 */
@Service
public class RescueTaskServiceImpl implements IRescueTaskService {
    @Autowired
    private RescueTaskMapper rescueTaskMapper;

    @Resource
    private ElasticsearchRestTemplate restTemplate;

    @Autowired
    private IShopService shopService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 查询应急救援管理
     *
     * @param id 应急救援管理主键
     * @return 应急救援管理
     */
    @Override
    public RescueTask selectRescueTaskById(Long id) {
        return rescueTaskMapper.selectRescueTaskById(id);
    }

    /**
     * 查询应急救援管理列表
     *
     * @param rescueTask 应急救援管理
     * @return 应急救援管理
     */
    @Override
    public List<RescueTask> selectRescueTaskList(RescueTask rescueTask) {
        return rescueTaskMapper.selectRescueTaskList(rescueTask);
    }

    /**
     * 新增应急救援管理
     *
     * @param rescueTask 应急救援管理
     * @return 结果
     */
    @Override
    public int insertRescueTask(RescueTask rescueTask) {
        return rescueTaskMapper.insertRescueTask(rescueTask);
    }

    /**
     * 修改应急救援管理
     *
     * @param rescueTask 应急救援管理
     * @return 结果
     */
    @Override
    public int updateRescueTask(RescueTask rescueTask) {
        return rescueTaskMapper.updateRescueTask(rescueTask);
    }

    /**
     * 批量删除应急救援管理
     *
     * @param ids 需要删除的应急救援管理主键
     * @return 结果
     */
    @Override
    public int deleteRescueTaskByIds(Long[] ids) {
        return rescueTaskMapper.deleteRescueTaskByIds(ids);
    }

    /**
     * 删除应急救援管理信息
     *
     * @param id 应急救援管理主键
     * @return 结果
     */
    @Override
    public int deleteRescueTaskById(Long id) {
        return rescueTaskMapper.deleteRescueTaskById(id);
    }

    /**
     * 用户端应急救援业务处理
     *
     * @param rescueTask 应急救援任务对象
     * @return TODO
     */
    @Override
    @Transactional
    public int createRescueTask(RescueTask rescueTask) {
        int num = insertRescueTask(rescueTask);
        if (num < 1) {
            throw new RuntimeException("发起救援失败");
        }
        // 通过地址解析工具类获取经纬度
        Map<String, Object> urlContent = AddressResolution.getURLContent(rescueTask.getPosition());
        // 通过redis的Geospatial地理位置类型筛选50km以内的商家,得到符合条件的商家列表
        double lng = Double.parseDouble(urlContent.get("lng").toString());
        double lat = Double.parseDouble(urlContent.get("lat").toString());
        List<Object> nearbyShops = getNearbyShops(lng, lat);
        if (nearbyShops.isEmpty()) {
            throw new RuntimeException("附近50公里范围内没有店铺");
        }
        List<Long> idList = new ArrayList<>();
        for (Object nearbyShop : nearbyShops) {
            idList.add(Long.parseLong(nearbyShop.toString()));
        }
        // 通过es查询店铺
        List<Shop> nearbyShopList = selectShop(idList);
        // 调用shopService业务层方法筛选有维修业务的店铺
        List<Shop> shopList = new ArrayList<>();
        for (Shop shop : nearbyShopList) {
            shopService.selectShopById(shop.getId());
            shopList.add(shop);
        }
        if (shopList.isEmpty()) {
            throw new RuntimeException("附近50公里没有符合救援条件的商家");
        }
        // 调用websocket给商家端发送消息
        notifyShops(shopList, rescueTask);
        // 将救援任务发送到RabbitMQ队列
        rabbitTemplate.convertAndSend("rescue_task_exchange", "rescue.task." + rescueTask.getId(), rescueTask, message -> {
            message.getMessageProperties().setHeader("taskId", rescueTask.getId());
            return message;
        });
        return num;
    }

    /**
     * 商家抢单后的后续业务处理
     *
     * @param rescueTask 应急救援管理
     * @return 结果
     */
    @Override
    @Transactional
    public int handleRescueTask(RescueTask rescueTask) {
        try {
            // 更新救援任务状态
            int num = rescueTaskMapper.updateRescueTask(rescueTask);
            if (num > 0) {
                // 从队列中移除消息
                boolean removed = removeMessageFromQueue(rescueTask.getId());
                RescueTask rescueTask1 = rescueTaskMapper.selectRescueTaskById(rescueTask.getId());
                if (removed) {
                    // 通知用户任务已被接受
                    notifyUserTaskAccepted(rescueTask1);
                    return num;
                } else {
                    // 如果消息已经不在队列中（可能已经进入死信队列），可以考虑回滚状态或其他处理
                    System.out.println("警告：消息已不在队列中，可能已超时");
                    Integer status = rescueTask1.getStatus();
                    if (status == 1){
                        throw new RuntimeException("任务被其他商家接单");
                    }
                    // 将订单状态设为超时
                    rescueTask.setStatus(2); // 假设2表示未接单状态
                    rescueTaskMapper.updateRescueTask(rescueTask);
                    throw new RuntimeException("任务已超时");
                }
            } else {
                throw new RuntimeException("更新救援任务失败");
            }
        } catch (Exception e) {
            // 处理异常，可以考虑重试或将任务发送到死信队列
            System.err.println("处理救援任务时发生错误：" + e.getMessage());
            e.printStackTrace();
            // 回滚事务
            throw new RuntimeException("处理救援任务时发生错误", e);
        }
    }

    /**
     * 封装方法,如果商家接单,从队列中移除消息
     *
     * @param taskId 救援任务编号
     */
    private boolean removeMessageFromQueue(final Long taskId) {
        Message message = rabbitTemplate.receive("rescue_task_queue");
        while (message != null) {
            Long messageId = (Long) message.getMessageProperties().getHeaders().get("taskId");
            if (taskId.equals(messageId)) {
                // 手动确认消息
                try {
                    Message finalMessage = message;
                    rabbitTemplate.execute(channel -> {
                        channel.basicAck(finalMessage.getMessageProperties().getDeliveryTag(), false);
                        return null;
                    });
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                    return false;
                }
            }
            message = rabbitTemplate.receive("rescue_task_queue");
        }
        return false;
    }

    /**
     * 封装es相关操作
     *
     * @param longitude 经度
     * @param latitude  纬度
     * @return 店铺集合
     */
    public List<Object> getNearbyShops(double longitude, double latitude) {
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates();
        GeoOperations<String, Object> geoOps = redisTemplate.opsForGeo();
        // 获取附近50km以内的商家
        GeoResults<RedisGeoCommands.GeoLocation<Object>> results = geoOps.radius("shops",
                new Circle(new Point(longitude, latitude), new Distance(50, Metrics.KILOMETERS)), args);
        // 处理结果集,将Map转换成List
        return results.getContent().stream()
                .map(result -> result.getContent().getName())
                .collect(Collectors.toList());
    }

    /**
     * 封装es相关操作
     *
     * @param idList id集合
     * @return 店铺集合
     */
    private List<Shop> selectShop(List<Long> idList) {
        IndexOperations indexOps = restTemplate.indexOps(Shop.class);
        // 判断shop对应的索引库是否存在：会请求es
        boolean exists = indexOps.exists();
        if (!exists) {
            //索引库不存在：创建索引库
            indexOps.create();
            //在内存中创建映射
            Document document = indexOps.createMapping();
            //将映射更新到es的索引库中
            indexOps.putMapping(document);
        }
        // 查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 设置查询类型：match、term、fuzzy、range...
        queryBuilder.withQuery(QueryBuilders.termsQuery("id", idList));
        // 执行查询
        SearchHits<Shop> searchHits = restTemplate.search(queryBuilder.build(), Shop.class, IndexCoordinates.of("shop"));
        List<Shop> shopList = new ArrayList<>();
        // 处理结果
        searchHits.getSearchHits().forEach(hit -> {
            shopList.add(hit.getContent());
        });
        return shopList;
    }

    /**
     * 封装websocket发送消息通知商家
     *
     * @param rescueTask 救援任务
     */
    private void notifyUserTaskAccepted(RescueTask rescueTask) {
        // 实现通知用户的逻辑
        String message = "您的救援任务已被接受，商家ID：" + rescueTask.getMId();
        try {
            WebSocketServer.sendInfo(message, rescueTask.getUId().toString());
        } catch (IOException e) {
            System.err.println("通知用户失败，用户ID：" + rescueTask.getUId() + "，原因：" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 封装websocket发送消息通知商家
     *
     * @param shops      店铺集合
     * @param rescueTask 救援任务
     */
    private void notifyShops(List<Shop> shops, RescueTask rescueTask) {
        for (Shop shop : shops) {
            try {
                WebSocketServer.sendInfo("救援请求: " + rescueTask.getPosition(), shop.getId().toString());
            } catch (IOException e) {
                System.err.println("通知商家失败，商家ID：" + shop.getId() + "，原因：" + e.getMessage());
                e.printStackTrace();
            }
        }
    }
}
